# GAME.PY - Game states and program instantiation.
# HIT3046 AI for GAMES
# ------- Assignment 2
# Authors: Mike Blackney, 0906123
#          Alex Bruce, 5409438

import time                  # We send the current and delta time to tick()
import pygame
from pygame.locals import *  # Useful constants and classes

import io
import pos
import gob
import gameobj
import gamemap
import utility

from defaults import *

class TitleScreen (gob.ACTION):
    "The title screen of the game."
    spr_title = None
    spr_start = None
    spr_exit  = None
    
    def __init__( self ):
        # Initialise pygame:
        io.FEEDBACK( ).Init( BG_COLOR_TITLE, SCREEN_SIZE )
        self.spr_title = io.SPRITE( "counterop.gif", 0 )
        self.spr_title.Position( (SCREEN_SIZE[0]/2,SCREEN_SIZE[1]/3 ) )
        self.spr_start = io.BUTTONSPRITE( "start.gif", "start_.gif", 0 )
        self.spr_start.Position( (SCREEN_SIZE[0]/2,SCREEN_SIZE[1]/3*2 \
                                 - self.spr_start.rect.height ) )
        self.spr_exit  = io.BUTTONSPRITE( "exit.gif", "exit_.gif", 0 )
        self.spr_exit.Position( (SCREEN_SIZE[0]/2,SCREEN_SIZE[1]/3*2 \
                                + self.spr_exit.rect.height ) )
    def Enter( self ):
        # Add our sprites:
        io.FEEDBACK( ).spriteList.add( self.spr_title )
        io.FEEDBACK( ).spriteList.add( self.spr_start )
        io.FEEDBACK( ).spriteList.add( self.spr_exit )
        # Set the background colour:
        io.FEEDBACK( ).FillBG( BG_COLOR_TITLE )
        # Show the mouse:
        pygame.mouse.set_visible(True)
    def Leave( self ):
        # Remove our sprites:
        io.FEEDBACK( ).spriteList.remove( self.spr_title )
        io.FEEDBACK( ).spriteList.remove( self.spr_start )
        io.FEEDBACK( ).spriteList.remove( self.spr_exit )
    def Tick( self, currTime, deltaTime ):
        # HANDLE INPUT:
        # Check for interface mouseclicks:
        for event in pygame.event.get():
            if event.type == MOUSEBUTTONDOWN:
                self.spr_start.TestPress( )
                self.spr_exit.TestPress( )
            elif event.type == MOUSEBUTTONUP:
                if self.spr_start.TestRelease( ):
                    self.parent.PushAction( GameAction( ) )
                    return
                if self.spr_exit.TestRelease( ):
                    print( "Exit!" )
                    self.parent.exit = True
                    return
            elif event.type == QUIT:
                self.parent.exit = True
                return
            elif event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    self.parent.exit = True
                    return

        # RENDER:
        io.FEEDBACK( ).Render( )
        # Flip:
        io.FEEDBACK( ).Update( )
        
    def Priority( self ):
        return 1;

class GameAction(gob.ACTION):
    "The in-game state."
    def Enter( self ):
        # Set the background colour:
        io.FEEDBACK( ).FillBG( BG_COLOR_GAME )
        # Hide the mouse:
        pygame.mouse.set_visible(False)
        # We view the sprites, not the costs, by default:
        self.costDisplay = False
        self.timeDisplay = False
        self.losDisplay =  False
        # Create the buildings:
        scr_centre = pos.Pos( (SCREEN_SIZE[0]/2,SCREEN_SIZE[1]/2) )
        buildings = ["FACILITY", \
                     "BARRACKS","BARRACKS","BARRACKS", \
                     "RADIOTOWER","RADIOTOWER","RADIOTOWER",\
                     "ENGINEERING", "MESSHALL" ]
        self.building_objs = list( );
        
        for building in buildings:
            # Make sure there aren't any conflicts:
            positioned = False
            while not positioned:
                # Position each building at the centre of the screen:
                position = scr_centre
                # Plus some random offset:
                position += pos.Pos( utility.RandomRange(-12,12)*25,
                                     utility.RandomRange(-8,8)*25 )
                # Ensure position is in 25-unit blocks:
                position = utility.ScreenToMap(position) * MAP_SCALE
                positioned = True
                for placed in self.building_objs:
                    if ( placed.location - position ).length( ) < BUILDING_DIST:
                        positioned = False
            self.building_objs.append( gameobj.BUILDING( BUILDING_DATA[building], position ) )

        # Init a font:
        self.sml_font = pygame.font.Font(None,14)
        self.lrg_font = pygame.font.Font(None,30)

        # Set the game time:
        self.game_time = 0
        self.winner = False

        # Initialise an empty guard list:
        self.Guards = []
        self.GuardSpawn = 0

        # Create a player:
        position = None
        self.Player = gameobj.SPY( (0,0) )
        while position is None or not self.Player.ValidMove( position ):
            position = pos.Pos( utility.RandomRange(0,SCREEN_SIZE[0]-1), \
                                utility.RandomRange(0,SCREEN_SIZE[1]-1) )
            self.Player.location = position

    def Priority( self ):
        return 0;
                    
    def Leave( self ):
        # Clear the list of game actors in case we want to start again:
        while len(gob.GAMEAGENT.agent_list) > 0:
            agent = gob.GAMEAGENT.agent_list[0]
            agent.Destroy( )
        # Clear the map:
        gamemap.MAP( ).Reset( )
        # Clear the building objects:
        while len( self.building_objs ) > 0:
            self.building_objs.pop( )
    def Tick( self, currTime, deltaTime ):
        # HANDLE INPUT:
        # Deal with events:
        for event in pygame.event.get():
            if event.type == MOUSEBUTTONDOWN:
                self.Player.MousePress( )
            elif event.type == MOUSEBUTTONUP:
                self.Player.MouseRelease( )
            elif event.type == MOUSEMOTION:
                self.Player.MouseMove( pygame.mouse.get_rel( ) )
            elif event.type == QUIT:
                self.parent.exit = True
                return
            elif event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    self.parent.PopAction( )
                    return
                if event.key == K_TAB:
                    self.costDisplay = True
                if event.key == K_BACKQUOTE:
                    self.timeDisplay = True
                if event.key == K_1:
                    self.losDisplay  = True
                else:
                    self.Player.KeyPress( event.key )
            elif event.type == KEYUP:
                if event.key == K_TAB:
                    self.costDisplay = False
                if event.key == K_BACKQUOTE:
                    self.timeDisplay = False
                if event.key == K_1:
                    self.losDisplay  = False
                else:
                    self.Player.KeyRelease( event.key )

        # Check for win condition:
        for location in self.building_objs:
            if location.deleted: self.building_objs.remove( location )
        if len( self.building_objs ) > 0:
            # Got some buildings left, so tick, etc.

            # OCCASIONALLY SPAWN A NEW GUARD IF WE'RE UNDERSTAFFED:
            # First cleanup:
            for guard in self.Guards:
                if (guard.deleted):
                    self.Guards.remove( guard )

            # Now if we're understaffed:
            if len(self.Guards) < NUM_GUARDS:
                self.GuardSpawn += deltaTime
                if self.GuardSpawn > GUARD_SPAWN_RATE:
                    self.GuardSpawn = 0
                    position = None
                    while position is None \
                          or gamemap.MAP( ).isBlocked( utility.ScreenToMap(position) ):
                        position = pos.Pos( utility.RandomRange(0,SCREEN_SIZE[0]-1), \
                                            utility.RandomRange(0,SCREEN_SIZE[1]-1) )
                    self.Guards.append( gameobj.GUARD( position ) )
    
            # MOVE ALL AGENTS:
            for agent in gob.GAMEAGENT.agent_list:
                agent.Tick( currTime, deltaTime )
            # Update the map:
            gamemap.MAP( ).Tick( currTime, deltaTime )
        else:
            # Got no buildings left.  We've won!
            self.winner = True

        # Display text:
        if DEBUG:
            for agent in  gob.GAMEAGENT.agent_list:
                agent.PrintText( self.sml_font, io.FEEDBACK( ).background )
        # RENDER:
        screen = io.FEEDBACK( ).screen
        if self.costDisplay == False \
           and self.timeDisplay == False \
           and self.losDisplay  == False:
            # Render sprites:
            io.FEEDBACK( ).Render( )
            # Display the UI:
            screen.fill( UI_BG, (10,8,50,4) )
            screen.fill( UI_BG, (10,18,50,4) )
            screen.fill( UI_FG, (12,6, \
                                 50 - self.Player.shoot_timeout / SHOOT_TIMEOUT * 50, 4) )
            screen.fill( UI_FG, (12,16, \
                                 50 - self.Player.bomb_timeout / BOMB_TIMEOUT * 50, 4) )
            string = str(int(self.game_time / 60))
            if self.game_time % 60 < 10:
                string += ":0"
            else:
                string += ":"
            string += str(int(self.game_time % 60))
            text = self.lrg_font.render( string, 1, UI_FG )
            textpos = text.get_rect(centerx=90,centery=15 )
            screen.blit( text, textpos )
            # If the player has won, display the win message:
            if self.winner:
                string = "Well done!  Your winning time was " + string
                text = self.lrg_font.render( string, 1, UI_FG )
                textpos = text.get_rect(centerx=SCREEN_SIZE[0]/2,centery=SCREEN_SIZE[1]/2 )
                screen.blit( text, textpos )
                
        else:
            screen.fill( BG_COLOR_GAME )
            if self.timeDisplay == True:
                maptimes = gamemap.MAP( ).lastpatrol
            elif self.losDisplay == True:
                maptimes = gamemap.MAP( ).loslines
            else:
                maptimes = gamemap.MAP( ).costs
            for y in range(MAP_SIZE[1]):
                for x in range(MAP_SIZE[0]):
                    map_time = maptimes[x + y * MAP_SIZE[0]]
                    tile_color = (0,map_time % 255,0)
                    if map_time == -1:
                        tile_color = (255,0,0)
                    screen.fill( tile_color, \
                                 (x * MAP_SCALE, y * MAP_SCALE, \
                                  MAP_SCALE, MAP_SCALE) )
                    
        gamemap.MAP( ).ResetLos( )
                    
        # Flip:
        io.FEEDBACK( ).Update( )

        if not self.winner:
            # Increase the game clock:
            self.game_time += deltaTime
            # If the player's been wounded, penalise him:
            self.game_time += self.Player.wounds
            self.Player.wounds = 0

class Game (gob.ACTIONAGENT):
    "This object represents the game"
    def __init__( self ):
        # Chain up:
        gob.ACTIONAGENT.__init__( self )
        # We're not about to exit, and we're in the title screen:
        self.exit = False
        self.PushAction( TitleScreen( ) )

def main( ):
    # The main program instantiation function.
    theGame = Game( )
    deltaTime = 0
    currTime = time.clock( )

    while (not theGame.exit):
        deltaTime = time.clock( ) - currTime
        currTime = time.clock( )
        theGame.Tick( currTime, deltaTime );

# Call main( ):
if __name__ == '__main__':
    main( )
